/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gtest/gtest.h"
#include "test_helper.h"

#include "plugins/ecmascript/runtime/ecma_string.h"
#include "include/coretypes/array.h"
#include "plugins/ecmascript/runtime/js_proxy.h"
#include "plugins/ecmascript/runtime/js_hclass.h"
#include "plugins/ecmascript/runtime/js_tagged_value-inl.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "include/runtime.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/ecma_runtime_call_info.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/global_env.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;

namespace ark::test {
class JSProxyTest : public testing::Test {
public:
    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ {};

private:
    PandaVM *instance_ {nullptr};
    ecmascript::EcmaHandleScope *scope_ {nullptr};
};

static JSFunction *JSObjectTestCreate(JSThread *thread)
{
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    JSHandle<GlobalEnv> globalEnv = ecmaVm->GetGlobalEnv();
    return globalEnv->GetObjectFunction().GetObject<JSFunction>();
}

TEST_F(JSProxyTest, ProxyCreate)
{
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>::Cast(dynclass), dynclass));

    JSHandle<JSTaggedValue> key(thread_->GetEcmaVM()->GetFactory()->NewFromString("x"));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));
    JSObject::SetProperty(thread_, targetHandle, key, value);
    EXPECT_EQ(JSObject::GetProperty(thread_, targetHandle, key).GetValue()->GetInt(), 1);

    JSHandle<JSTaggedValue> handlerHandle(
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>::Cast(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);

    EXPECT_EQ(JSProxy::GetProperty(thread_, proxyHandle, key).GetValue()->GetInt(), 1);
    PropertyDescriptor desc(thread_);
    JSProxy::GetOwnProperty(thread_, proxyHandle, key, desc);
    EXPECT_EQ(desc.GetValue()->GetInt(), 1);
}

// ES6 9.5.8 [[Get]] (P, Receiver)
JSTaggedValue HandlerGetProperty([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    // NOLINTNEXTLINE(readability-magic-numbers)
    return JSTaggedValue(10);
}

TEST_F(JSProxyTest, GetProperty)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // 1. handler has no "get"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> key(factory->NewFromString("x"));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));
    JSObject::SetProperty(thread_, targetHandle, key, value);
    EXPECT_EQ(JSObject::GetProperty(thread_, targetHandle, key).GetValue()->GetInt(), 1);

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);

    EXPECT_EQ(JSProxy::GetProperty(thread_, proxyHandle, key).GetValue()->GetInt(), 1);

    // 2. handler has "get"
    EcmaVM *vm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = vm->GetGlobalEnv();
    JSHandle<JSTaggedValue> getKey = thread_->GlobalConstants()->GetHandledGetString();
    JSHandle<JSTaggedValue> getHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerGetProperty)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), getKey, getHandle);

    JSHandle<JSProxy> proxyHandle2(JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle));
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    JSHandle<JSTaggedValue> key2(factory->NewFromString("y"));
    EXPECT_EQ(JSProxy::GetProperty(thread_, proxyHandle2, key2).GetValue()->GetInt(), 10);
}

// ES6 9.5.5 [[GetOwnProperty]] (P)
JSTaggedValue HandlerGetOwnProperty([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(JSTaggedValue::Undefined());
}

TEST_F(JSProxyTest, GetOwnProperty)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // 1. handler has no "get"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> key(factory->NewFromString("x"));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));
    JSObject::SetProperty(thread_, targetHandle, key, value);
    EXPECT_EQ(JSObject::GetProperty(thread_, targetHandle, key).GetValue()->GetInt(), 1);

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);

    PropertyDescriptor desc(thread_);
    JSProxy::GetOwnProperty(thread_, proxyHandle, key, desc);
    EXPECT_EQ(desc.GetValue()->GetInt(), 1);

    // 2. handler has "get"
    EcmaVM *vm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = vm->GetGlobalEnv();
    JSHandle<JSTaggedValue> defineKey = thread_->GlobalConstants()->GetHandledGetOwnPropertyDescriptorString();
    JSHandle<JSTaggedValue> defineHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerGetOwnProperty)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), defineKey, defineHandle);

    JSHandle<JSProxy> proxyHandle2 = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    JSHandle<JSTaggedValue> key2(factory->NewFromString("y"));
    PropertyDescriptor desc2(thread_);
    EXPECT_FALSE(JSProxy::GetOwnProperty(thread_, proxyHandle2, key2, desc2));
}

// ES6 9.5.9 [[Set]] ( P, V, Receiver)
JSTaggedValue HandlerSetProperty([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(JSTaggedValue::False());
}

TEST_F(JSProxyTest, SetProperty)
{
    // 1. handler has no "get"
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // 1. handler has no "get"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> key(factory->NewFromString("x"));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);

    EXPECT_TRUE(JSProxy::SetProperty(thread_, proxyHandle, key, value));
    EXPECT_EQ(JSProxy::GetProperty(thread_, proxyHandle, key).GetValue()->GetInt(), 1);
    EXPECT_EQ(JSObject::GetProperty(thread_, targetHandle, key).GetValue()->GetInt(), 1);

    // 2. handler has "set"
    EcmaVM *vm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = vm->GetGlobalEnv();
    JSHandle<JSTaggedValue> setKey = thread_->GlobalConstants()->GetHandledSetString();
    JSHandle<JSTaggedValue> setHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerSetProperty)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), setKey, setHandle);

    JSHandle<JSProxy> proxyHandle2(JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle));
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    // NOLINTNEXTLINE(readability-magic-numbers)
    JSHandle<JSTaggedValue> value2(thread_, JSTaggedValue(10));
    EXPECT_FALSE(JSProxy::SetProperty(thread_, proxyHandle2, key, value2));
    EXPECT_EQ(JSProxy::GetProperty(thread_, proxyHandle2, key).GetValue()->GetInt(), 1);
}

// ES6 9.5.6 [[DefineOwnProperty]] (P, Desc)
JSTaggedValue HandlerDefineOwnProperty([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(JSTaggedValue::False());
}

TEST_F(JSProxyTest, DefineOwnProperty)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // 1. handler has no "defineProperty"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> key(factory->NewFromString("x"));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);

    PropertyDescriptor desc(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)));
    EXPECT_TRUE(JSProxy::DefineOwnProperty(thread_, proxyHandle, key, desc));
    EXPECT_EQ(JSProxy::GetProperty(thread_, proxyHandle, key).GetValue()->GetInt(), 1);
    EXPECT_EQ(JSObject::GetProperty(thread_, targetHandle, key).GetValue()->GetInt(), 1);

    // 2. handler has "defineProperty"
    EcmaVM *vm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = vm->GetGlobalEnv();
    JSHandle<JSTaggedValue> setKey = thread_->GlobalConstants()->GetHandledDefinePropertyString();
    JSHandle<JSTaggedValue> setHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerDefineOwnProperty)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), setKey, setHandle);

    JSHandle<JSProxy> proxyHandle2 = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    // NOLINTNEXTLINE(readability-magic-numbers)
    PropertyDescriptor desc2(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(10)));
    EXPECT_FALSE(JSProxy::DefineOwnProperty(thread_, proxyHandle, key, desc2));
    EXPECT_EQ(JSProxy::GetProperty(thread_, proxyHandle2, key).GetValue()->GetInt(), 1);
}

JSTaggedValue HandlerDeleteProperty([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(JSTaggedValue::False());
}

// ES6 9.5.10 [[Delete]] (P)
TEST_F(JSProxyTest, DeleteProperty)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // 1. handler has no "deleteProperty"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> key(factory->NewFromString("x"));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);

    PropertyDescriptor desc(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)), true, true, true);
    EXPECT_TRUE(JSProxy::DefineOwnProperty(thread_, proxyHandle, key, desc));
    EXPECT_EQ(JSProxy::GetProperty(thread_, proxyHandle, key).GetValue()->GetInt(), 1);
    EXPECT_EQ(JSObject::GetProperty(thread_, targetHandle, key).GetValue()->GetInt(), 1);
    EXPECT_TRUE(JSProxy::DeleteProperty(thread_, proxyHandle, key));
    PropertyDescriptor resDesc(thread_);
    JSProxy::GetOwnProperty(thread_, proxyHandle, key, resDesc);
    EXPECT_TRUE(JSTaggedValue::SameValue(resDesc.GetValue().GetTaggedValue(), JSTaggedValue::Undefined()));

    // 2. handler has "deleteProperty"
    EcmaVM *vm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = vm->GetGlobalEnv();
    JSHandle<JSTaggedValue> funcKey = thread_->GlobalConstants()->GetHandledDeletePropertyString();
    JSHandle<JSTaggedValue> funcHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerDeleteProperty)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), funcKey, funcHandle);

    JSHandle<JSProxy> proxyHandle2 = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    PropertyDescriptor desc2(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)), true, true, true);
    EXPECT_TRUE(JSProxy::DefineOwnProperty(thread_, proxyHandle2, key, desc2));
    EXPECT_EQ(JSProxy::GetProperty(thread_, proxyHandle2, key).GetValue()->GetInt(), 1);
    EXPECT_FALSE(JSProxy::DeleteProperty(thread_, proxyHandle2, key));
}

JSTaggedValue HandlerGetPrototype([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(JSTaggedValue::Null());
}

// ES6 9.5.1 [[GetPrototypeOf]] ( )
TEST_F(JSProxyTest, GetPrototypeOf)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // 1. handler has no "GetPrototypeOf"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> proto(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle->IsECMAObject());
    JSObject::SetPrototype(thread_, JSHandle<JSObject>(targetHandle), proto);
    EXPECT_TRUE(
        JSTaggedValue::SameValue(JSHandle<JSObject>(targetHandle)->GetPrototype(thread_), proto.GetTaggedValue()));

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);

    EXPECT_TRUE(JSTaggedValue::SameValue(JSProxy::GetPrototype(thread_, proxyHandle), proto.GetTaggedValue()));

    // 2. handler has "GetPrototypeOf"
    EcmaVM *vm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = vm->GetGlobalEnv();
    JSHandle<JSTaggedValue> funcKey = thread_->GlobalConstants()->GetHandledGetPrototypeOfString();
    JSHandle<JSTaggedValue> funcHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerGetPrototype)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), funcKey, funcHandle);

    JSHandle<JSProxy> proxyHandle2 = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    EXPECT_TRUE(JSTaggedValue::SameValue(JSProxy::GetPrototype(thread_, proxyHandle2), JSTaggedValue::Null()));
}

JSTaggedValue HandlerSetPrototype([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(JSTaggedValue::False());
}

// ES6 9.5.2 [[SetPrototypeOf]] (V)
TEST_F(JSProxyTest, SetPrototypeOf)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // 1. handler has no "SetPrototypeOf"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> proto(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);

    JSProxy::SetPrototype(thread_, proxyHandle, proto);
    EXPECT_TRUE(
        JSTaggedValue::SameValue(JSHandle<JSObject>(targetHandle)->GetPrototype(thread_), proto.GetTaggedValue()));

    // 2. handler has "SetPrototypeOf"
    EcmaVM *vm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = vm->GetGlobalEnv();
    JSHandle<JSTaggedValue> funcKey = thread_->GlobalConstants()->GetHandledSetPrototypeOfString();
    JSHandle<JSTaggedValue> funcHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerSetPrototype)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), funcKey, funcHandle);

    JSHandle<JSProxy> proxyHandle2 = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    EXPECT_FALSE(JSProxy::SetPrototype(thread_, proxyHandle2, proto));
}

JSTaggedValue HandlerIsExtensible([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(JSTaggedValue::False());
}

// ES6 9.5.3 [[IsExtensible]] ( )
TEST_F(JSProxyTest, IsExtensible)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // 1. handler has no "IsExtensible"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);

    bool status1 = JSProxy::IsExtensible(thread_, proxyHandle);
    bool status2 = JSHandle<JSObject>::Cast(targetHandle)->IsExtensible();
    EXPECT_TRUE(status1 == status2);

    // 2. handler has "IsExtensible"
    EcmaVM *vm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = vm->GetGlobalEnv();
    JSHandle<JSTaggedValue> funcKey = thread_->GlobalConstants()->GetHandledIsExtensibleString();
    JSHandle<JSTaggedValue> funcHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerIsExtensible)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), funcKey, funcHandle);

    JSHandle<JSProxy> proxyHandle2 = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    EXPECT_FALSE(JSProxy::IsExtensible(thread_, proxyHandle2));
}

JSTaggedValue HandlerPreventExtensions([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(JSTaggedValue::False());
}

// ES6 9.5.4 [[PreventExtensions]] ( )
TEST_F(JSProxyTest, PreventExtensions)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // 1. handler has no "PreventExtensions"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);

    bool status1 = JSProxy::PreventExtensions(thread_, proxyHandle);
    EXPECT_TRUE(status1);
    bool status2 = JSHandle<JSObject>::Cast(targetHandle)->IsExtensible();
    EXPECT_FALSE(status2);

    // 2. handler has "PreventExtensions"
    EcmaVM *vm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = vm->GetGlobalEnv();
    JSHandle<JSTaggedValue> funcKey = thread_->GlobalConstants()->GetHandledPreventExtensionsString();
    JSHandle<JSTaggedValue> funcHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerPreventExtensions)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), funcKey, funcHandle);

    JSHandle<JSProxy> proxyHandle2 = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    EXPECT_FALSE(JSProxy::PreventExtensions(thread_, proxyHandle2));
}

JSTaggedValue HandlerHasProperty([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(JSTaggedValue::False());
}

// ES6 9.5.7 [[HasProperty]] (P)
TEST_F(JSProxyTest, HasProperty)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // 1. handler has no "HasProperty"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> key(factory->NewFromString("x"));
    PropertyDescriptor desc(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)));
    JSObject::DefineOwnProperty(thread_, JSHandle<JSObject>::Cast(targetHandle), key, desc);

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);

    EXPECT_TRUE(JSProxy::HasProperty(thread_, proxyHandle, key));

    // 2. handler has "HasProperty"
    EcmaVM *vm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = vm->GetGlobalEnv();
    JSHandle<JSTaggedValue> funcKey = thread_->GlobalConstants()->GetHandledHasString();
    JSHandle<JSTaggedValue> funcHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerHasProperty)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), funcKey, funcHandle);

    JSHandle<JSProxy> proxyHandle2 = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    EXPECT_FALSE(JSProxy::HasProperty(thread_, proxyHandle2, key));
}

JSTaggedValue HandlerOwnPropertyKeys([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    auto thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSArray> arr = factory->NewJSArray();
    return JSTaggedValue(arr.GetTaggedValue());
}

// ES6 9.5.12 [[OwnPropertyKeys]] ()
TEST_F(JSProxyTest, OwnPropertyKeys)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // 1. handler has no "OwnPropertyKeys"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> key(factory->NewFromString("x"));
    PropertyDescriptor desc(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)));
    JSObject::DefineOwnProperty(thread_, JSHandle<JSObject>::Cast(targetHandle), key, desc);

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);
    JSHandle<TaggedArray> res = JSProxy::OwnPropertyKeys(thread_, proxyHandle);

    EXPECT_TRUE(JSTaggedValue::SameValue(res->Get(0), key.GetTaggedValue()));

    // 2. handler has "OwnPropertyKeys"
    // create new empty target so we wont throw TypeError at (18. - 21.)
    JSHandle<JSTaggedValue> targetHandle2(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(targetHandle2->IsECMAObject());
    EcmaVM *vm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = vm->GetGlobalEnv();
    JSHandle<JSTaggedValue> funcKey = thread_->GlobalConstants()->GetHandledOwnKeysString();
    JSHandle<JSTaggedValue> funcHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerOwnPropertyKeys)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), funcKey, funcHandle);

    JSHandle<JSProxy> proxyHandle2 = JSProxy::ProxyCreate(thread_, targetHandle2, handlerHandle);
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    JSHandle<TaggedArray> res2 = JSProxy::OwnPropertyKeys(thread_, proxyHandle2);
    EXPECT_TRUE(res2->GetLength() == 0 || !JSTaggedValue::SameValue(res2->Get(0), key.GetTaggedValue()));
}

JSTaggedValue HandlerCall([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(JSTaggedValue::False());
}
JSTaggedValue HandlerFunction([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(JSTaggedValue::True());
}

// ES6 9.5.13 [[Call]] (thisArgument, argumentsList)
TEST_F(JSProxyTest, Call)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    // 1. handler has no "Call"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerFunction)));
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    JSTaggedValue res;
    EXPECT_TRUE(*proxyHandle != nullptr);
    {
        auto info = NewRuntimeCallInfo(thread_, proxyHandle, JSHandle<JSTaggedValue>::Cast(proxyHandle),
                                       JSTaggedValue::Undefined(), 0);
        res = JSProxy::CallInternal(info.Get());
    }
    JSHandle<JSTaggedValue> taggedRes(thread_, res);

    EXPECT_TRUE(JSTaggedValue::SameValue(taggedRes.GetTaggedValue(), JSTaggedValue::True()));

    // 2. handler has "Call"
    JSHandle<JSTaggedValue> funcKey = thread_->GlobalConstants()->GetHandledApplyString();
    JSHandle<JSTaggedValue> funcHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerCall)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), funcKey, funcHandle);

    JSHandle<JSProxy> proxyHandle2 = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    JSTaggedValue res2;
    {
        auto info = NewRuntimeCallInfo(thread_, proxyHandle2, JSHandle<JSTaggedValue>::Cast(proxyHandle2),
                                       JSTaggedValue::Undefined(), 0);
        res2 = JSProxy::CallInternal(info.Get());
    }
    JSHandle<JSTaggedValue> taggedRes2(thread_, res2);

    EXPECT_TRUE(JSTaggedValue::SameValue(taggedRes2.GetTaggedValue(), JSTaggedValue::False()));
}

JSTaggedValue HandlerConstruct([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    auto thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> dynclass(thread, JSObjectTestCreate(thread));
    JSHandle<JSTaggedValue> obj(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));

    JSHandle<JSTaggedValue> key(factory->NewFromString("x"));
    PropertyDescriptor desc(thread, JSHandle<JSTaggedValue>(thread, JSTaggedValue(2)));
    JSObject::DefineOwnProperty(argv->GetThread(), JSHandle<JSObject>::Cast(obj), key, desc);
    return JSTaggedValue(obj.GetTaggedValue());
}
JSTaggedValue HandlerConFunc([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    auto thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> dynclass(thread, JSObjectTestCreate(thread));
    JSHandle<JSTaggedValue> obj(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));

    JSHandle<JSTaggedValue> key(factory->NewFromString("x"));
    PropertyDescriptor desc(thread, JSHandle<JSTaggedValue>(thread, JSTaggedValue(1)));
    JSObject::DefineOwnProperty(argv->GetThread(), JSHandle<JSObject>::Cast(obj), key, desc);
    return JSTaggedValue(obj.GetTaggedValue());
}

// ES6 9.5.14 [[Construct]] ( argumentsList, new_target)
TEST_F(JSProxyTest, Construct)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    // 1. handler has no "Construct"
    JSHandle<JSTaggedValue> dynclass(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> targetHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerConFunc)));
    JSHandle<JSFunction>::Cast(targetHandle)->GetJSHClass()->SetConstructor(true);
    EXPECT_TRUE(targetHandle->IsECMAObject());

    JSHandle<JSTaggedValue> handlerHandle(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    EXPECT_TRUE(handlerHandle->IsECMAObject());

    JSHandle<JSProxy> proxyHandle = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle != nullptr);
    JSTaggedValue res;
    {
        auto info = NewRuntimeCallInfo(thread_, proxyHandle, JSTaggedValue::Undefined(), targetHandle, 0);
        res = JSProxy::ConstructInternal(info.Get());
    }
    JSHandle<JSTaggedValue> taggedRes(thread_, res);
    JSHandle<JSTaggedValue> key(factory->NewFromCanBeCompressString("x"));
    EXPECT_EQ(JSObject::GetProperty(thread_, taggedRes, key).GetValue()->GetInt(), 1);

    // 2. handler has "Construct"
    JSHandle<JSTaggedValue> funcKey = thread_->GlobalConstants()->GetHandledProxyConstructString();
    JSHandle<JSTaggedValue> funcHandle(factory->NewJSFunction(env, reinterpret_cast<void *>(HandlerConstruct)));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(handlerHandle), funcKey, funcHandle);

    JSHandle<JSProxy> proxyHandle2 = JSProxy::ProxyCreate(thread_, targetHandle, handlerHandle);
    EXPECT_TRUE(*proxyHandle2 != nullptr);
    JSTaggedValue res2;
    {
        auto info = NewRuntimeCallInfo(thread_, proxyHandle2, JSTaggedValue::Undefined(), targetHandle, 0);
        res2 = JSProxy::ConstructInternal(info.Get());
    }
    JSHandle<JSTaggedValue> taggedRes2(thread_, res2);
    EXPECT_EQ(JSObject::GetProperty(thread_, taggedRes2, key).GetValue()->GetInt(), 2);
}
}  // namespace ark::test
